using System;
using System.Activities.Statements;
using System.Collections.Generic;
using System.Linq;
using Headsight.BPMN2WF.BPMN.Utils;
using Headsight.BPMN2WF.Transformation;

namespace Headsight.BPMN2WF.WF.Simplification
{
    public class WFSimplifierFlowchart : IWFSimplifier
    {
        private IBPMN2WFTransformationContext _ctx;
        private object _bpmnTransformationResultFlowIn;
        private object _bpmnTransformationResultFlowOut;

        public BPMNTransformationResult Simplify(IBPMN2WFTransformationContext ctx, BPMNTransformationResult transformationResult)
        {
            _ctx = ctx;

            Func<FlowNode, bool> isFlowchart = x => x is FlowStep && ((FlowStep)x).Action is Flowchart;
            var flowchart = (Flowchart)transformationResult.Result;

            if (!flowchart.Nodes.Any(isFlowchart))
            {
                return transformationResult;
            }

            var bpmnTransformationResultResult = flowchart;
            _bpmnTransformationResultFlowIn = transformationResult.FlowIn;
            _bpmnTransformationResultFlowOut = transformationResult.FlowOut;

            var flowchartNodes = new List<FlowNode>(flowchart.Nodes);
            flowchart.Nodes.Clear();

            Recursive(flowchart, flowchart.StartNode, Children(flowchart.StartNode));

            flowchart.Nodes.Add(flowchart.StartNode);

            return new BPMNTransformationResult(flowchart, _bpmnTransformationResultFlowIn, 
                _bpmnTransformationResultFlowOut);
        }

        private void Recursive(Flowchart flowchart, FlowNode parent, IEnumerable<FlowNode> children)
        {
            foreach (var eachChild in children)
            {
                Recursive(flowchart, eachChild, Children(eachChild));
                Simplify(flowchart, parent, eachChild);
            }
        }

        private void Simplify(Flowchart flowchart, FlowNode parent, FlowNode eachChild)
        {
            Func<FlowNode, bool> isFlowchart = x => x is FlowStep && ((FlowStep)x).Action is Flowchart;
            
            if (isFlowchart(eachChild))
            {
                if (parent is FlowSwitch<String>)
                {
                    var parentFlowSwitch = (FlowSwitch<String>)parent;

                    if (parentFlowSwitch.Cases.Any(x => x.Value == eachChild))
                    {
                        // There are potential multiple branches pointing to the same child... :-|
                        var cases = new List<KeyValuePair<String, FlowNode>>(
                            parentFlowSwitch.Cases.Where(x => x.Value == eachChild));
                        var flowchartNext = ((FlowStep)eachChild).Next;

                        var imploded = Implode((Flowchart)((FlowStep)eachChild).Action);

                        foreach (var each in cases)
                        {
                            parentFlowSwitch.Cases[each.Key] = imploded.Item1;
                        }
                        foreach (var each in imploded.Item2)
                        {
                            flowchart.Nodes.Add(each);
                        }

                        try
                        {
                            if (imploded.Item3 != null)
                            {
                                ((FlowStep)imploded.Item3).Next = flowchartNext;
                            }
                        }
                        catch (Exception e)
                        {
                            throw e;
                        }
                        if (_bpmnTransformationResultFlowIn == eachChild)
                        {
                            _bpmnTransformationResultFlowIn = imploded.Item1;
                        }
                        if (_bpmnTransformationResultFlowOut == eachChild)
                        {
                            _bpmnTransformationResultFlowOut = imploded.Item3;
                        }

                        var eachChildFlowchart = (Flowchart)((FlowStep)eachChild).Action;
                        foreach (var each in eachChildFlowchart.Variables)
                        {
                            if (!flowchart.Variables.Contains(each))
                            {
                                flowchart.Variables.Add(each);
                            }
                        }

                    }
                    else if (parentFlowSwitch.Default == eachChild)
                    {
                        var flowchartNext = ((FlowStep)eachChild).Next;
                        var imploded = Implode((Flowchart)((FlowStep)eachChild).Action);

                        parentFlowSwitch.Default = imploded.Item1;
                        foreach (var each in imploded.Item2)
                        {
                            flowchart.Nodes.Add(each);
                        }
                        if (imploded.Item3 != null)
                        {
                            ((FlowStep)imploded.Item3).Next = flowchartNext;
                        }

                        if (_bpmnTransformationResultFlowIn == eachChild)
                        {
                            _bpmnTransformationResultFlowIn = imploded.Item1;
                        }
                        if (_bpmnTransformationResultFlowOut == eachChild)
                        {
                            _bpmnTransformationResultFlowOut = imploded.Item3;
                        }

                        var eachChildFlowchart = (Flowchart)((FlowStep)eachChild).Action;
                        foreach (var each in eachChildFlowchart.Variables)
                        {
                            if (!flowchart.Variables.Contains(each))
                            {
                                flowchart.Variables.Add(each);
                            }
                        }

                    }
                }
                // eachChild is Flowchart and parent any non-FlowSwitch FlowNode
                else
                {
                    var eachChildFlowchart = (Flowchart)((FlowStep)eachChild).Action;
                    var parentFlowStep = (FlowStep) parent;
                    var parentFlowStepNext = parentFlowStep.Next;
                    var imploded = Implode(eachChildFlowchart);

                    foreach (var each in eachChildFlowchart.Variables)
                    {
                        if (!flowchart.Variables.Contains(each))
                        {
                            flowchart.Variables.Add(each);
                        }
                    }

                    parentFlowStep.Next = imploded.Item1;
                    foreach (var each in imploded.Item2)
                    {
                        flowchart.Nodes.Add(each);
                    }
                    if (imploded.Item3 != null)
                    {
                        ((FlowStep) imploded.Item3).Next = parentFlowStepNext;
                    }

                    if (_bpmnTransformationResultFlowIn == eachChild)
                    {
                        _bpmnTransformationResultFlowIn = imploded.Item1;
                    }
                    if (_bpmnTransformationResultFlowOut == eachChild)
                    {
                        _bpmnTransformationResultFlowOut = imploded.Item3;
                    }
                }
            }
            else 
            {
                flowchart.Nodes.Add(eachChild);
            }
        }

        private Tuple<FlowNode, IList<FlowNode>, FlowNode> Implode(Flowchart flowchart)
        {
            var keyValuePair = _ctx.BPMNTransformationResults.First(x => x.Value.Result.Equals(flowchart));

            var formerBPMNTransResult = keyValuePair.Value;

            return new Tuple<FlowNode, IList<FlowNode>, FlowNode>(
                flowchart.StartNode,
                flowchart.Nodes,
                (FlowNode)formerBPMNTransResult.FlowOut);
        }

        public bool Probe(IBPMN2WFTransformationContext ctx, BPMNTransformationResult transformationResult)
        {
            return transformationResult.Result is Flowchart;
        }


        private static IEnumerable<FlowNode> Children(FlowNode flowNode)
        {
            if (flowNode == null)
            {
                return new List<FlowNode>();
            }

            if (flowNode is FlowStep)
            {
                if (((FlowStep)flowNode).Next == null)
                {
                    return new List<FlowNode>();
                }
                return new List<FlowNode> { ((FlowStep)flowNode).Next };
            }

            if (flowNode is FlowSwitch<String>)
            {
                var result = new List<FlowNode>();

                ((FlowSwitch<String>)flowNode).Cases.ForEach(x => result.Add(x.Value));

                if (((FlowSwitch<String>)flowNode).Default != null)
                {
                    result.Add(((FlowSwitch<String>)flowNode).Default);
                }

                return result;
            }

            throw new NotSupportedException(String.Format("FlowNode type '{0}' currently not supported.", flowNode));
        }
    }
}